﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MealBooking.Core.Models.Meals;
using System.IO;
using System.Xml.Serialization;
using System.Xml.Linq;
using MealBooking.Core.Models.Dishes;
using MealBooking.Core.Services.Interface.MealService;
using MealBooking.Core.Common;

namespace MealBooking.Core.Services.Impl.MealService
{
    public class MealService : IMealService
    {
        #region private
        static string mealFolderPath = Path.Combine(Utils.Utils.StorageLocation(), "Meals");
        static string mealFilePath = Path.Combine(mealFolderPath, "meals.xml");
        static string MealWithTemplateFilePath = Path.Combine(mealFolderPath, "MealsWithTemplate.xml");
        static string MealCategoryFilePath = Path.Combine(mealFolderPath, "MealCategories/MealCategories.xml");
        static string MealTemplateFilePath = Path.Combine(mealFolderPath, "MealTemplates/MealTemplates.xml");

        private static object _mealLocker = new object();
        private static object _mealCategoryLocker = new object();
        private static object _mealTemplateLocker = new object();
        private static object _mealWithTemplateLocker = new object();
        #endregion

        #region Meal

        #region private
        private XDocument LoadMealFile()
        {
            return Utils.Utils.InitXmlFile(mealFilePath);
        }
        private void SaveMealFile(XDocument doc)
        {
            lock (_mealLocker)
            {
                doc.Save(mealFilePath);
            }
        }
        #endregion

        #region private
        private bool ValidateDishTemplates(MealCategory mealCategory, List<Dish> dishes)
        {
            if (mealCategory == null || dishes == null || dishes.Count == 0)
            {
                return false;
            }
            var dishTemplates = mealCategory.DishTemplates;
            //总和不相等:
            int dishSumInTemplate = dishTemplates.Sum(t => t.DishSum);
            if (dishSumInTemplate != dishes.Count)
            {
                return false;
            }
            var groupedDishes = dishes.GroupBy(d => d.Category.Id).OrderBy(g => g);
            int categorySum = dishTemplates.Count;
            //分类个数不相等:
            if (categorySum != groupedDishes.Count())
            {
                return false;
            }
            dishTemplates.OrderBy(d => d.Category.Id);
            for (int i = 0; i < categorySum; i++)
            {
                if (!dishTemplates.ElementAt(i).Category.Id.Equals(groupedDishes.ElementAt(i).Key))
                {
                    return false;
                }
                if (dishTemplates.ElementAt(i).DishSum != groupedDishes.ElementAt(i).Count())
                {
                    return false;
                }
            }
            return true;
        }
        private Meal AddNewMealIfNotExist(Meal newMeal)
        {
            var doc = LoadMealFile();
            var xmlMeals = doc.Root.Elements("Meal");
            var tempMeals = ModelSerializer.Deserialize<Meal>(xmlMeals);
            if (tempMeals != null && tempMeals.Count != 0)
            {
                foreach (var tempMeal in tempMeals)
                {
                    if (tempMeal.Equals(newMeal))
                    {
                        return tempMeal;
                    }
                }
            }
            // add new meal to file:
            newMeal.Id = Guid.NewGuid().ToString().ToLower();
            var frag = ModelSerializer.Serialize(newMeal);
            doc.Root.Add(frag);
            SaveMealFile(doc);
            return newMeal;
        }
        #endregion
        public Meal MealDetail(string mealId)
        {
            var doc = LoadMealFile();
            var xmlMeal = doc.Root.Elements("Meal").SingleOrDefault(m =>
                mealId.Equals(m.Element("Id").Value));
            return ModelSerializer.Deserialize<Meal>(xmlMeal);
        }
        public List<Meal> MealDetails(List<string> mealIds)
        {
            if (mealIds == null || mealIds.Count == 0)
            {
                return null;
            }
            var doc = LoadMealFile();
            var xmlMeals = doc.Root.Elements("Meal").Where(m =>
                mealIds.Contains(m.Element("Id").Value));
            return ModelSerializer.Deserialize<Meal>(xmlMeals);
        }
        public MealWithSum OrderNewMeal(Meal newMeal)
        {
            if (newMeal == null || newMeal.Category == null || newMeal.Category.Id == null
            || newMeal.Dishes == null || newMeal.Dishes.Count == 0)
            {
                return null;
            }
            var mealCategory = MealCategoryDetail(newMeal.Category.Id);
            var newDishIds = newMeal.Dishes.Select(d => d.Id).ToList();
            var newDishes = CoreService.DishService.DishDetails(newDishIds);
            //validate :
            var result = ValidateDishTemplates(mealCategory, newDishes);
            if (!result)
            {
                return null;
            }
            //init meal:
            mealCategory.DishCategories = null; mealCategory.DishTemplates = null;
            var dishes = newDishes.Select(d => new Dish { Id = d.Id, Name = d.Name, Price = d.Price }).ToList();
            Meal meal = new Meal
            {
                Category = mealCategory,
                Dishes = dishes,
            };
            meal = AddNewMealIfNotExist(meal);
            //init mealsWithSum:
            MealWithSum mealsWithSum = new MealWithSum { MealSum = 1, Meal = meal };
            return mealsWithSum;
        }
        #endregion

        #region MealCategory

        #region private
        private static XDocument LoadMealCategoryFile()
        {
            return Utils.Utils.InitXmlFile(MealCategoryFilePath);
        }
        private static void SaveMealCategoryFile(XDocument doc)
        {
            Utils.Utils.SaveFile(_mealCategoryLocker, doc, MealCategoryFilePath);
        }
        #endregion

        public List<MealCategory> AllMealCategories()
        {
            var doc = LoadMealCategoryFile();
            var xmlMealCategories = doc.Root.Elements("MealCategory");
            return ModelSerializer.Deserialize<MealCategory>(xmlMealCategories);
        }
        public List<MealCategory> AllMealCategoriesDishTemplatesNotNull()
        {
            var doc = LoadMealCategoryFile();
            var xmlMealCategories = doc.Root.Elements("MealCategory").Where(c =>
                c.Element("DishTemplates") != null && c.Element("DishTemplates").Elements("DishTemplate").Count() != 0);
            return ModelSerializer.Deserialize<MealCategory>(xmlMealCategories);
        }
        public MealCategory IsMealCategoryNameInUse(string newMealCategoryName)
        {
            var doc = LoadMealCategoryFile();
            var xmlMealCategory = doc.Root.Elements("MealCategory").SingleOrDefault(c =>
                newMealCategoryName.Equals(c.Element("Name").Value));
            return ModelSerializer.Deserialize<MealCategory>(xmlMealCategory);
        }
        public MealCategory MealCategoryDetail(string mealCategoryId)
        {
            var doc = LoadMealCategoryFile();
            var xmlMealCategory = doc.Root.Elements("MealCategory").SingleOrDefault(c =>
                mealCategoryId.Equals(c.Element("Id").Value));
            var mealCategory = ModelSerializer.Deserialize<MealCategory>(xmlMealCategory);
            return mealCategory;
        }
        public MealCategory AddNewMealCategory(MealCategory newMealCategory)
        {
            newMealCategory.Id = Guid.NewGuid().ToString().ToLower();
            return UpdateMealCategory(newMealCategory);
        }
        public MealCategory UpdateMealCategory(MealCategory newMealCategory)
        {
            var doc = LoadMealCategoryFile();
            var xmlMealCategory = doc.Root.Elements("MealCategory").SingleOrDefault(c =>
                newMealCategory.Id.Equals(c.Element("Id").Value));
            if (xmlMealCategory != null)
            {
                xmlMealCategory.Remove();
            }
            //update dish category in old meal category:
            CoreService.DishService.UpdateDishCategoriesInMealCategory(newMealCategory);
            #region dishCategories
            if (newMealCategory.DishTemplates != null && newMealCategory.DishTemplates.Count != 0)
            {
                var dishCategoryIds = newMealCategory.DishTemplates.Select(t => t.Category.Id).ToList();
                //remove dish categories from other meal category:
                var oldDishCategories = doc.Descendants("DishCategory").Where(c =>
                    dishCategoryIds.Contains(c.Element("Id").Value));
                if (oldDishCategories != null && oldDishCategories.Count() != 0)
                {
                    oldDishCategories.Remove();
                }
                var oldDishTemplates = doc.Descendants("DishTemplate").Where(t =>
                    dishCategoryIds.Contains(t.Element("Category").Element("Id").Value));
                if (oldDishTemplates != null && oldDishTemplates.Count() != 0)
                {
                    oldDishTemplates.Remove();
                }
                var newDishCategories = CoreService.DishService.DishCategoryDetails(dishCategoryIds);
                newDishCategories.OrderBy(c => c.Id);
                newMealCategory.DishTemplates.OrderBy(t => t.Category.Id);
                for (int i = 0; i < newDishCategories.Count; i++)
                {
                    newMealCategory.DishTemplates[i].Category = new DishCategory { Id = newDishCategories[i].Id, Name = newDishCategories[i].Name };
                }
            }
            #endregion
            xmlMealCategory = ModelSerializer.Serialize(newMealCategory);
            doc.Root.Add(xmlMealCategory);
            SaveMealCategoryFile(doc);
            return MealCategoryDetail(newMealCategory.Id);
        }
        #endregion

        #region DishCategories in MealCategory
        public List<DishCategory> DishCategoriesInMealCategory(string mealCategoryId)
        {
            return CoreService.DishService.DishCategoriesInMealCategory(mealCategoryId);
        }
        public void AddDishCategoryToMealCategory(string mealCategoryId, DishCategory newDishCategory)
        {
            var mealCategory = MealCategoryDetail(mealCategoryId);
            var dishTemplates = mealCategory.DishTemplates;
            var dishTemplate = new DishTemplate
            {
                Category = new DishCategory { Id = newDishCategory.Id, Name = newDishCategory.Name },
                DishSum = 1,
            };
            if (dishTemplates != null && dishTemplates.Count != 0)
            {
                var dishCategoryIds = dishTemplates.Select(t => t.Category.Id).ToList();
                if (!dishCategoryIds.Contains(newDishCategory.Id))
                {
                    mealCategory.DishTemplates.Add(dishTemplate);
                    UpdateMealCategory(mealCategory);
                }
            }
            else
            {
                mealCategory.DishTemplates.Add(dishTemplate);
                UpdateMealCategory(mealCategory);
            }
        }
        #endregion

        #region Dishes in MealCategory
        public Dictionary<DishTemplate, List<Dish>> DishTemplatesInMealCategory(string mealCategoryId)
        {
            var mealCategory = MealCategoryDetail(mealCategoryId);
            var dishesInMealCategory = CoreService.DishService.DishesInMealCategory(mealCategoryId);
            var dishTemplates = mealCategory.DishTemplates;
            var dishesInMealCategoryWithTemplate =
                from dishTemplate in dishTemplates
                from dishCategory in dishesInMealCategory
                where dishTemplate.Category.Id.Equals(dishCategory.Key.Id)
                select new KeyValuePair<DishTemplate, List<Dish>>(dishTemplate, dishCategory.Value);
            return dishesInMealCategoryWithTemplate.ToDictionary(k => k.Key, v => v.Value);
        }
        #endregion
    }
}
